/**
 *
 */
package jmine.tec.report.impl;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.report.ExecutableDataColumn;
import jmine.tec.report.ExecutableDataRow;
import jmine.tec.report.cell.DataCell;
import jmine.tec.report.cell.impl.StringDataCell;
import jmine.tec.report.listener.RowListener;
import jmine.tec.report.metadata.ColumnMetaData;
import jmine.tec.report.metadata.ParameterDefinition;
import jmine.tec.report.metadata.impl.UnmodifiableColumnMetaData;
import jmine.tec.utils.collection.impl.ClassHierarchyMap;

/**
 * @author takeshi
 * @version 22/08/2007
 */
public abstract class AbstractExecutableDataColumn extends SimpleDataColumn implements ExecutableDataColumn {

    private static final DataCell EMPTY_DATA_CELL = new StringDataCell(" ");

    /**
     * Cria um {@link ColumnMetaData} nao modificavel base, com base nos parametros passados
     * 
     * @param colKey a chave da coluna
     * @param colName o nome da coluna
     * @param para parametro obrigatorio unico
     * @param others mais parametros
     * @return {@link ColumnMetaData}
     */
    protected static ColumnMetaData createMetaData(final String colKey, final String colName,
            final ParameterDefinition para, final ParameterDefinition... others) {
        final ParameterDefinition[] array = new ParameterDefinition[others.length + 1];
        array[0] = para;
        for (int i = 0; i < others.length; i++) {
            array[i + 1] = others[i];
        }
        return new UnmodifiableColumnMetaData(colKey, colName, array);
    }

    // private final Map<K, V>

    /**
     * Cria um {@link ColumnMetaData} nao modificavel base, com base nos parametros passados
     * 
     * @param colKey a chave da coluna
     * @param colName o nome da coluna
     * @param base base column metadata
     * @param para parametro obrigatorio unico
     * @param others mais parametros
     * @return {@link ColumnMetaData}
     */
    protected static ColumnMetaData getBaseMetaData(final String colKey, final String colName,
            final ColumnMetaData base, final ParameterDefinition para, final ParameterDefinition... others) {
        final ParameterDefinition[] array = new ParameterDefinition[others.length + 1];
        array[0] = para;
        for (int i = 0; i < others.length; i++) {
            array[i + 1] = others[i];
        }
        return new UnmodifiableColumnMetaData(colKey, colName, base, array);
    }

    private final Map<Class<?>, RowListener> rowListeners = new ClassHierarchyMap<RowListener>();

    private final RowListener defaultRowListener = new RowListener() {
        public void executeDataRow(final ExecutableDataRow theRow, final Map<String, Object> parameter) {
            AbstractExecutableDataColumn.this.executeDefaultRow(theRow, parameter);
        }
    };

    /**
     * C'tor
     * 
     * @param key chave da coluna para
     * @param name nome da coluna - usado como label
     * @param metaData o {@link ColumnMetaData}
     * @param hashMap the default parameter values
     * @param otherParams other parameters to append to this column metadata
     */
    public AbstractExecutableDataColumn(final String key, final String name, final ColumnMetaData metaData,
            final Map<String, Object> hashMap, final ParameterDefinition... otherParams) {
        super(key, name, metaData, hashMap, otherParams);
        this.registerRowListener(Object.class, this.defaultRowListener);
    }

    /**
     * C'tor
     * 
     * @param key the column key
     * @param name the column name
     * @param metaData the base metadata
     * @param otherParams other parameters to append to this column metadata
     */
    public AbstractExecutableDataColumn(final String key, final String name, final ColumnMetaData metaData,
            final ParameterDefinition... otherParams) {
        super(key, name, metaData, otherParams);
        this.registerRowListener(Object.class, this.defaultRowListener);
    }

    /**
     * C'tor
     * 
     * @param key String
     * @param name String
     * @param params Parameters
     */
    public AbstractExecutableDataColumn(final String key, final String name, final ParameterDefinition... params) {
        super(key, name, params);
        this.registerRowListener(Object.class, this.defaultRowListener);
    }

    /**
     * Executes this data column. This method does NOT recurse into child rows. Its responsability of the listeners to do so.
     * 
     * @param type the type
     * @param row the {@link ExecutableDataRow}
     * @param params the params
     * @see br.com.maps.util.report.ExecutableDataColumn#executeByObjectType(java.lang.Class, br.com.maps.util.report.ExecutableDataRow,
     * java.util.Map)
     */
    public final void executeByObjectType(final Class<?> type, final ExecutableDataRow row,
            final Map<String, Object> params) {
        final ExecutableDataRow toUse = this.beforeExecution(row, params);
        try {
            final RowListener rowListener = this.rowListeners.get(type);
            if (rowListener != null) {
                rowListener.executeDataRow(toUse, params);
            } else {
                this.executeDefaultRow(toUse, params);
            }
        } finally {
            this.afterExecution(toUse, params);
        }
    }

    /**
     * Hook method for subclasses to modify the map of parameters or the {@link ExecutableDataRow} before this column is executed
     * 
     * @param theRow {@link ExecutableDataRow}
     * @param params {@link Map} of parameters
     * @return the {@link ExecutableDataRow} to use
     */
    protected ExecutableDataRow beforeExecution(final ExecutableDataRow theRow, final Map<String, Object> params) {
        return theRow;
    }

    /**
     * Hook method for subclasses that want to modify the {@link ExecutableDataRow} after this column has been executed.
     * 
     * @param theRow the {@link ExecutableDataRow}
     * @param params the {@link Map} of parameters
     */
    protected void afterExecution(final ExecutableDataRow theRow, final Map<String, Object> params) {
    }

    /**
     * Fallback method for when a listener for the given {@link ExecutableDataRow} is not found. The default implementation simply calls
     * {@link #getEmptyDataCell()} and adds it to the row and recurses child rows.
     * 
     * @param row the {@link ExecutableDataRow}
     * @param params the map of parameters
     */
    protected void executeDefaultRow(final ExecutableDataRow row, final Map<String, Object> params) {
        row.addCell(this.getEmptyDataCell());
        this.recurseChildRows(row, params);
    }

    /**
     * Returns an empty data cell, in the sense of this column. Subclasses are allowed to override this method for proper behaviour.
     * 
     * @return an emtpy {@link DataCell}
     */
    protected DataCell getEmptyDataCell() {
        return EMPTY_DATA_CELL;
    }

    /**
     * Register a row listener.
     * 
     * @param type the type to register the row listener to
     * @param rowListener the row listener to be executed
     */
    public final synchronized void registerRowListener(final Class<?> type, final RowListener rowListener) {
        this.rowListeners.put(type, rowListener);
    }

    /**
     * @param row {@link ExecutableDataRow}
     * @param params the param map
     */
    protected final void recurseChildRows(final ExecutableDataRow row, final Map<String, Object> params) {
        final Map<String, Object> toUse = params == null ? new HashMap<String, Object>() : params;
        final List<ExecutableDataRow> children = row.getExecutableDataRows();
        // for (ExecutableDataRow child : children) {
        // child.execute(this, toUse);
        // }
        if (!children.isEmpty()) {
            for (final ExecutableDataRow executableDataRow : children) {
                executableDataRow.execute(this, toUse);
            }
        }

    }

    /**
     * Executa todas as linhas fornecidas no contexto destes par�metros.
     * 
     * @param rows as linhas
     * @param params os par�metros
     */
    public void execute(final List<? extends ExecutableDataRow> rows, final Map<String, Object> params) {
        if (rows.isEmpty()) {
            return;
        }
        for (final ExecutableDataRow executableDataRow : rows) {
            executableDataRow.execute(this, params);
        }
    }

    /**
     * Gets a parameter value, default to this column's default value.
     * 
     * @param paramName the parameter name
     * @param param the param map
     * @return {@link Object} or <code>null</code> if none found
     */
    protected final Object getParameterValue(final String paramName, final Map<String, Object> param) {
        Object obj = param == null ? null : param.get(paramName);
        if (obj == null) {
            obj = this.getDefaultParameterValue(paramName);
        }
        return obj;
    }

    /**
     * @param type the type
     * @return boolean
     * @see br.com.maps.util.report.ExecutableDataColumn#reactsTo(java.lang.Class)
     */
    public boolean reactsTo(final Class<?> type) {
        RowListener rowListener = this.rowListeners.get(type);
        return rowListener != null && rowListener != this.defaultRowListener;
    }

}
